package org.visionlibrary.image.geomtric.lines.compare;

import java.awt.Rectangle;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.visionlibrary.image.geomtric.lines.Lines;
import org.visionlibrary.image.geomtric.lines.compare.model.AbstractSegmentCompare;
import org.visionlibrary.image.geomtric.lines.compare.model.HistogramCompare;
import org.visionlibrary.image.geomtric.lines.histogram.CenterOfGravityLineLength;
import org.visionlibrary.image.geomtric.lines.histogram.CenterOfGravityLineSlope;
import org.visionlibrary.image.geomtric.lines.histogram.LineLength;
import org.visionlibrary.image.geomtric.lines.histogram.LineSlope;
import org.visionlibrary.image.geomtric.model.Point2d;
import org.visionlibrary.image.geomtric.model.Vector2d;
import org.visionlibrary.image.geomtric.util.Utils;
import org.visionlibrary.image.model.Histogram;


/**
 * Eksperymentalna wersja algorytmu porownywania zestawow odcinkow, pozwala na 
 * dokladniejsze sterowanie wartosciami, posiada wymienny interfejs okreslajacy
 * metode porownywania histogramu.
 * <br /><br />
 * Pozwala na realizacje porownania za pomoca czterech roznych metod porownywania
 * dwie z metod opisane sa w pracy, dwie pozostale sa pewnymi wariantami modyfikacji.
 */
public class ExperimentalEnergyFunction extends AbstractSegmentCompare {
	protected HistogramCompare compareMethod;
	
	//domyslne wartosci wag
	protected double weight1 = 25;
	protected double weight2 = 25;
	protected double weight3 = 25;
	protected double weight4 = 25;

	public ExperimentalEnergyFunction(HistogramCompare compareMethod,
			double weight1, double weight2, double weight3, double weight4) {
		super();
		this.compareMethod = compareMethod;
		this.weight1 = weight1;
		this.weight2 = weight2;
		this.weight3 = weight3;
		this.weight4 = weight4;
	}

	@Override
	public double compare(List<Vector2d> linelist1, List<Vector2d> linelist2) {
		double s1 = overallAngleSimilarity(linelist1, linelist2);
		double s2 = overallLineVicinitySimilarity(linelist1, linelist2);
		double s3 = overallLengthSimilarity(linelist1, linelist2);
		double s4 = overallLengthVicinitySimilarity(linelist1, linelist2);
		return (weight1 * s1 + weight2 * s2 + weight3 * s3 + weight4 * s4)
				/ (weight1 + weight2 + weight3 + weight4);
	}

	@Override
	public Map<String, Double> compareDetails(List<Vector2d> linelist1,
			List<Vector2d> linelist2) {
		Map<String, Double> result = new LinkedHashMap<String, Double>();
		double s1 = overallAngleSimilarity(linelist1, linelist2);
		double s2 = overallLineVicinitySimilarity(linelist1, linelist2);
		double s3 = overallLengthSimilarity(linelist1, linelist2);
		double s4 = overallLengthVicinitySimilarity(linelist1, linelist2);

		double all = (weight1 * s1 + weight2 * s2 + weight3 * s3 + weight4 * s4)
				/ (weight1 + weight2 + weight3 + weight4);

		result.put("AeS", s1);
		result.put("LiVS", s2);
		result.put("LnS", s3);
		result.put("LnVS", s4);
		result.put("E", all);

		return result;
	}
	
	private double overallAngleSimilarity(List<Vector2d> linelist1,
			List<Vector2d> linelist2) {
		int histogramSize = 720;

		Histogram ah1 = (new LineSlope(histogramSize).process(linelist1));
		Histogram nah1 = ah1.getNormalized();

		Histogram ah2 = (new LineSlope(histogramSize).process(linelist2));
		Histogram nah2 = ah2.getNormalized();

		double similarity = similarityMatchAcrossShifts(nah1, nah2,
				compareMethod);

		return similarity;
	}

	private double overallLineVicinitySimilarity(List<Vector2d> linelist1,
			List<Vector2d> linelist2) {
		int histogramSize = 1440;
		double maxOverallLineVicinitySimilarity = 0;

		int qiter = 1;
		int qiterStep = 1;
		int qend = 2;

		Point2d c1 = Lines.getWeightedCentroid(linelist1);
		Point2d c2 = Lines.getWeightedCentroid(linelist2);

		while (qiter <= qend) {
			Histogram ahc1 = (new CenterOfGravityLineSlope(c1,
					(int) histogramSize / qiter)).process(linelist1);
			Histogram nahc1 = ahc1.getNormalized();

			Histogram ahc2 = (new CenterOfGravityLineSlope(c2,
					(int) histogramSize / qiter)).process(linelist2);
			Histogram nahc2 = ahc2.getNormalized();

			double similarity = similarityMatchAcrossShifts(nahc1, nahc2,
					compareMethod);
			if (similarity > maxOverallLineVicinitySimilarity) {
				maxOverallLineVicinitySimilarity = similarity;
			}

			qiter += qiterStep;
		}

		return maxOverallLineVicinitySimilarity;
	}

	private double overallLengthSimilarity(List<Vector2d> linelist1,
			List<Vector2d> linelist2) {
		int histogramSize = 720;

		double ratio = Utils.roundDouble(Lines.scaleDifference(linelist1,
				linelist2), 4);
		
		List<Vector2d> scaledlinelist2 = Lines.scale(linelist2, ratio);
		
		Rectangle bounds = Lines.findBoundingRect(linelist1);
		Rectangle bounds2 = Lines.findBoundingRect(scaledlinelist2);

		Vector2d vc1 = new Vector2d(bounds.x, bounds.y, bounds.x
				+ bounds.width, bounds.y + bounds.height);
		Vector2d vc2 = new Vector2d(bounds2.x, bounds2.y, bounds2.x
				+ bounds2.width, bounds2.y + bounds2.height);
		
		double length = vc1.length() + 1;
		double length2 = vc2.length() + 1;
		
		double mlength = Math.ceil(length);
		if (mlength < Math.ceil(length2))
			mlength = Math.ceil(length2);

		Histogram ah1 = (new LineLength((int) Math.ceil(mlength), histogramSize))
				.process(linelist1);
		Histogram nah1 = ah1.getNormalized();

		Histogram ah2 = (new LineLength((int) Math.ceil(mlength), histogramSize))
				.process(scaledlinelist2);
		Histogram nah2 = ah2.getNormalized();
		
		double similarity = similarityMatchAcrossShifts(nah1, nah2,
				compareMethod);

		return similarity;
	}

	private double overallLengthVicinitySimilarity(List<Vector2d> linelist1,
			List<Vector2d> linelist2) {
		int histogramSize = 1440;
		double maxOverallLengthSimilarity = 0;

		int qiter = 1;
		int qiterStep = 1;
		int qend = 2;

		double ratio = Utils.roundDouble(Lines.scaleDifference(linelist1,
				linelist2), 4);
		List<Vector2d> scaledlinelist2 = Lines.scale(linelist2, ratio);

		Rectangle bounds = Lines.findBoundingRect(linelist1);
		Rectangle bounds2 = Lines.findBoundingRect(scaledlinelist2);

		double length = new Vector2d(bounds.x, bounds.y, bounds.x
				+ bounds.width, bounds.y + bounds.height).length() + 1;
		double length2 = new Vector2d(bounds2.x, bounds2.y, bounds2.x
				+ bounds2.width, bounds2.y + bounds2.height).length() + 1;

		double mlength = Math.ceil(length);
		if (mlength < Math.ceil(length2))
			mlength = Math.ceil(length2);

		Point2d c1 = Lines.getWeightedCentroid(linelist1);
		Point2d c2 = Lines.getWeightedCentroid(scaledlinelist2);

		while (qiter <= qend) {
			Histogram ah1 = (new CenterOfGravityLineLength(c1, (int) Math
					.ceil(mlength), (int) histogramSize / qiter))
					.process(linelist1);
			Histogram nah1 = ah1.getNormalized();

			Histogram ah2 = (new CenterOfGravityLineLength(c2, (int) Math
					.ceil(mlength), (int) histogramSize / qiter))
					.process(scaledlinelist2);
			Histogram nah2 = ah2.getNormalized();

			double similarity = similarityMatchAcrossShifts(nah1, nah2,
					compareMethod);
			if (similarity > maxOverallLengthSimilarity) {
				maxOverallLengthSimilarity = similarity;
			}

			qiter += qiterStep;
		}

		return maxOverallLengthSimilarity;
	}

	private double similarityMatchAcrossShifts(Histogram h1, Histogram h2,
			HistogramCompare compareMethod) {
		double maximumSimilarity = 0;
		int siter = 0;
		int send = h1.getNumBins();

		Histogram h2copy = Histogram.copy(h2);
		while (siter < send) {
			double similarity = compareMethod.distance(h1, h2copy);
			if (similarity > maximumSimilarity) {
				maximumSimilarity = similarity;
			}

			h2copy = Histogram.getShiftedHistogramRightByOne(h2copy);
			siter++;
		}
		
		return maximumSimilarity;
	}
}
